package ycl.security.handler;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import ycl.common.constants.ExceptionConstant;
import ycl.common.constants.RedisConstant;
import ycl.security.service.LoginService;
import ycl.system.entity.SysUser;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

/**
 * 认证用户提供者
 *
 * @author ycl
 * @date 2022-03-10 13:47:02
 */
@Slf4j
@Service("authUserDetailProvider")
public class AuthUserDetailProvider extends AbstractUserDetailsAuthenticationProvider {

	@Resource
	private LoginService loginService;
	@Resource
	private StringRedisTemplate stringRedisTemplate;
	@Resource
	private PasswordEncoder passwordEncoder;

	/**
	 * 附加检查
	 *
	 * @param userDetails
	 * @param authentication
	 * @throws AuthenticationException
	 */
	@Override
	protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		//密码错时记录错误次数
		if (!userDetails.getPassword().equals(authentication.getCredentials())) {
			Long count = getWrongCount(userDetails.getUsername());
			//超出登录次数上限就抛出上锁异常
			if (count >= RedisConstant.LOGIN_FAIL_COUNT)
				throw new BadCredentialsException(ExceptionConstant.USER_LOCK);
			throw new BadCredentialsException(ExceptionConstant.USER_ACCOUNT_PASSWORD_WRONG);
		}
	}


	private Long getWrongCount(String username) {
		//如果没有设置成1
		Long increment = stringRedisTemplate.opsForValue().increment(RedisConstant.getUserLoginFailKey(username));
		//登录错误次数超过配置值就上锁
		if (increment != null && increment >= RedisConstant.LOGIN_FAIL_COUNT) {
			stringRedisTemplate.opsForValue().set(
					RedisConstant.getUserLockKey(username),
					"1",
					RedisConstant.LOCK_MINUTE,
					TimeUnit.MINUTES
			);
		}
		return increment;
	}


	/**
	 * 检索用户, 用户登录
	 *
	 * @param username
	 * @param authentication
	 * @return
	 * @throws AuthenticationException
	 */
	@Override
	protected UserDetails retrieveUser(
			String username,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		log.info("AuthUserDetailProvider -- retrieveUser -- 登录 === {}", username);
		SysUser sysUser = loginService.getUserByUsername(username);
		String name = sysUser.getUsername();



		String expire = stringRedisTemplate.opsForValue().get(RedisConstant.getUserLockKey(name));
		//用户被锁定
		if (StrUtil.isNotBlank(expire))
			throw new BadCredentialsException(ExceptionConstant.USER_LOCK);

		//TODO 校验密码

		return new User(name, passwordEncoder.encode(sysUser.getPassword()), new ArrayList<>());
	}
}
